package fons.model.ontology.requirement;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.log4j.Logger;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import fons.model.ontology.Context;
import fons.model.ontology.Dimension;
import fons.model.ontology.Filter;
import fons.model.ontology.Filters;
import fons.model.ontology.Value;

public abstract class AbstractRequirement implements Requirement {
	private final static Logger logger = Logger.getLogger(AbstractRequirement.class);
	
	protected Context context;
	private final Set<Dimension> dimensions = Sets.newHashSet();
	private final Set<Dimension> filters = Sets.newHashSet();
	private final List<Set<Dimension>> matches = Lists.newArrayList();
	private final Set<Dimension> targetDimensions = Sets.newHashSet();

	@Override
	public void setContext(Context context) {
		this.context = context;
	}

	@Override
	public Requirement of(Set<Dimension> dimensions) {
		this.dimensions.addAll(dimensions);
		return this;
	}

	@Override
	public Requirement in(Set<Dimension> dimensions) {
		if (dimensions != null)
			this.filters.addAll(dimensions);
		return this;
	}

	@Override
	public Requirement as(Dimension dimension) {
		this.targetDimensions.add(dimension);
		return this;
	}

	@Override
	public Requirement as(Set<Dimension> dimensions) {
		if (dimensions != null)
			this.targetDimensions.addAll(dimensions);
		return this;
	}

	@Override
	public Requirement each(List<Set<Dimension>> matching) {
		for (Set<Dimension> dims: matching) {
			this.matches.add(Sets.newHashSet(dims));
		}
		return this;
	}

	@Override
	public Collection<Value> reduceAll(Set<Value> v) {
		final List<Value> retVal = Lists.newArrayList();
		final Filter filter = Filters.anyOf(this.dimensions)
				.and(Filters.anyOf(this.filters));
		final List<Value> values = Lists.newArrayList(filter.filter(v));
		
		final Map<Set<Dimension>, Set<Value>> sorted = Maps.newHashMap();
		Set<Dimension> tmp = Sets.newHashSetWithExpectedSize(matches.size());
		logger.info("Berechnung \"" + this.getName() + "\" betrifft bis zu " + tmp.size() + " Werte");
		
		withNextValue: for (Value value: values) {			
			tmp.clear();
			for (Set<Dimension> match: matches) {
				boolean ok = false;
				check: for (Dimension valueDim: value.getDimensions()) {
					if (match.contains(valueDim)) {
						tmp.add(valueDim);
						ok = true;
						break check;
					}
				}
				if (!ok) continue withNextValue;
			}
			
			if (sorted.containsKey(tmp)) {
				sorted.get(tmp).add(value);
			} else {
				sorted.put(Sets.newHashSet(tmp), Sets.newHashSet(value));
			}
		}
		logger.info("Berechnung abgeschlossen");


		for (Entry<Set<Dimension>, Set<Value>> entry: sorted.entrySet()) {
			final Value newValue = this.reduce(entry.getValue());
			if (newValue == Context.missingValue)
				continue;

			for (Dimension matchingDimension: entry.getKey()) {
				newValue.as(matchingDimension);
			}

			for (Dimension targetDimension: this.targetDimensions) {
				newValue.as(targetDimension);
			}

			retVal.add(newValue);
		}

		return retVal;
	}

	protected abstract Value reduce(Set<Value> values);

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((dimensions == null) ? 0 : dimensions.hashCode());
		result = prime * result + ((filters == null) ? 0 : filters.hashCode());
		result = prime * result + ((matches == null) ? 0 : matches.hashCode());
		result = prime
				* result
				+ ((targetDimensions == null) ? 0 : targetDimensions.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		AbstractRequirement other = (AbstractRequirement) obj;
		if (dimensions == null) {
			if (other.dimensions != null)
				return false;
		} else if (!dimensions.equals(other.dimensions))
			return false;
		if (filters == null) {
			if (other.filters != null)
				return false;
		} else if (!filters.equals(other.filters))
			return false;
		if (matches == null) {
			if (other.matches != null)
				return false;
		} else if (!matches.equals(other.matches))
			return false;
		if (targetDimensions == null) {
			if (other.targetDimensions != null)
				return false;
		} else if (!targetDimensions.equals(other.targetDimensions))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "AbstractRequirement [dimensions=" + dimensions + ", filters="
				+ filters + ", matches=" + matches + ", targetDimensions="
				+ targetDimensions + "]";
	}

	@Override
	public String getName() {
		StringBuilder builder = new StringBuilder();
		boolean firstTime = true;
		for (Dimension dim: targetDimensions) {
			if (!firstTime) {
				builder.append(", ");
			}
			builder.append(dim.getName());
			firstTime = false;
		}
		return builder.toString();
	}

}
